using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Reflection;

namespace QiHe.ReliableData
{
    public class XmlDeSerialization
    {
        public static Package DeSerialize(string xmlfile)
        {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(xmlfile);

            if (xmldoc.DocumentElement.Name == "Package")
            {
                Package package = new Package();
                ReadDataTypes(package, xmldoc.DocumentElement.FirstChild);
                ReadEnumTypes(package, xmldoc.DocumentElement.FirstChild.NextSibling);
                package.BuildTypes();
                ReadDataObjects(package, xmldoc.DocumentElement.LastChild);
                return package;
            }
            return null;
        }

        private static void ReadEnumTypes(Package package, System.Xml.XmlNode xmlNode)
        {
            if (xmlNode.Name == "EnumTypes")
            {
                foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
                {
                    EnumType enumType = (EnumType)ReadDataOjbect(typeof(EnumType), node);
                    package.EnumTypes.Add(enumType);
                }
            }
        }

        private static void ReadDataObjects(Package package, System.Xml.XmlNode xmlNode)
        {
            if (xmlNode.Name == "DataObjects")
            {
                foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
                {
                    Type type = Package.Types[node.Name];
                    package.DataObjects.Add(ReadDataOjbect(type, node));
                }
            }
        }

        private static object ReadDataOjbect(Type type, System.Xml.XmlNode xmlNode)
        {
            if (xmlNode.FirstChild != null && xmlNode.FirstChild.NodeType == XmlNodeType.Text)
            {
                return ConvertValue(type, xmlNode.InnerText);
            }
            if (type == typeof(string)) return String.Empty;
            object data = Activator.CreateInstance(type);
            foreach (XmlAttribute xmlAttribute in xmlNode.Attributes)
            {
                MemberInfo[] members = type.GetMember(xmlAttribute.Name,
                    MemberTypes.Field | MemberTypes.Property,
                    BindingFlags.Public | BindingFlags.Instance);
                if (members.Length == 1)
                {
                    SetMemberData(data, xmlAttribute.Value, members[0]);
                }
                else if (members.Length > 1)
                {
                    foreach (MemberInfo mi in members)
                    {
                        if (mi.GetCustomAttributes(typeof(XmlAttributeAttribute), false).Length >= 1)
                        {
                            SetMemberData(data, xmlAttribute.Value, mi);
                        }
                    }
                }
            }

            List<string> listTypeNodeNames = new List<string>();

            MemberInfo[] listTypeMembers = type.FindMembers(
                    MemberTypes.Field | MemberTypes.Property,
                BindingFlags.Public | BindingFlags.Instance,
                delegate(MemberInfo mi, object obj)
                {
                    return IsGenericList(mi);
                },
                null
                );
            foreach (MemberInfo mi in listTypeMembers)
            {
                Type memberType = GetMemberDataType(mi);
                object memberIns = Activator.CreateInstance(memberType);
                Type elementType = memberType.GetGenericArguments()[0];
                MethodInfo addMethod = memberType.GetMethod("Add", new Type[] { elementType });
                object[] attributes = mi.GetCustomAttributes(typeof(XmlElementAttribute), false);
                if (attributes.Length >= 1)
                {
                    string itemName = ((XmlElementAttribute)attributes[0]).ElementName;
                    listTypeNodeNames.Add(itemName);
                    foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
                    {
                        if (node.Name == itemName)
                        {
                            addMethod.Invoke(memberIns, new object[]{
                                    ReadDataOjbect(elementType, node)});
                        }
                    }
                }
                else
                {
                    listTypeNodeNames.Add(mi.Name);
                    foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
                    {
                        if (node.Name == mi.Name)
                        {
                            foreach (System.Xml.XmlNode subnode in node.ChildNodes)
                            {
                                addMethod.Invoke(memberIns, new object[]{
                                    ReadDataOjbect(elementType, subnode)});
                            }
                        }
                    }
                }
                SetMemberData(data, mi, memberIns);
            }

            foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
            {
                if (listTypeNodeNames.Contains(node.Name)) continue;
                MemberInfo[] members = type.GetMember(node.Name,
                    MemberTypes.Field | MemberTypes.Property,
                    BindingFlags.Public | BindingFlags.Instance);
                foreach (MemberInfo mi in members)
                {
                    SetMemberData(data, node, mi);
                }
            }
            return data;
        }

        private static bool IsGenericList(MemberInfo mi)
        {
            Type memberType = GetMemberDataType(mi);
            Type enumerable = memberType.GetInterface("IEnumerable");
            return enumerable != null && memberType.IsGenericType;
        }

        private static Type GetMemberDataType(MemberInfo mi)
        {
            switch (mi.MemberType)
            {
                case MemberTypes.Field:
                    FieldInfo fi = mi as FieldInfo;
                    return fi.FieldType;
                case MemberTypes.Property:
                    PropertyInfo pi = mi as PropertyInfo;
                    return pi.PropertyType;
                default:
                    return null;
            }
        }

        private static void SetMemberData(object data, System.Xml.XmlNode node, MemberInfo mi)
        {
            Type memberType = GetMemberDataType(mi);
            object memberData = ReadDataOjbect(memberType, node);
            SetMemberData(data, mi, memberData);
        }

        private static void SetMemberData(object data, string text, MemberInfo mi)
        {
            Type memberType = GetMemberDataType(mi);
            object memberData = ConvertValue(memberType, text);
            SetMemberData(data, mi, memberData);
        }

        private static void SetMemberData(object data, MemberInfo mi, object value)
        {
            switch (mi.MemberType)
            {
                case MemberTypes.Field:
                    FieldInfo fi = mi as FieldInfo;
                    fi.SetValue(data, value);
                    break;
                case MemberTypes.Property:
                    PropertyInfo pi = mi as PropertyInfo;
                    pi.SetValue(data, value, null);
                    break;
            }
        }

        static object ConvertValue(Type type, string text)
        {
            if (type == typeof(int))
            {
                return int.Parse(text);
            }
            else if (type == typeof(bool))
            {
                return bool.Parse(text);
            }
            else if (type == typeof(float))
            {
                return float.Parse(text);
            }
            else if (type.IsEnum)
            {
                return Enum.Parse(type, text);
            }
            else
            {
                return text;
            }
        }

        private static void ReadDataTypes(Package package, System.Xml.XmlNode xmlNode)
        {
            if (xmlNode.Name == "DataTypes")
            {
                foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
                {
                    package.DataTypes.Add(ReadDataType(node));
                    //package.DataTypes.Add((DataType)ReadDataOjbect(typeof(DataType), node));
                }
            }
        }

        private static DataType ReadDataType(System.Xml.XmlNode xmlNode)
        {
            DataType dataType = new DataType();
            dataType.Name = xmlNode.Attributes["Name"].Value;
            foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
            {
                dataType.Fields.Add(ReadTypeField(node));
            }
            return dataType;
        }

        private static TypeField ReadTypeField(System.Xml.XmlNode xmlNode)
        {
            TypeField field = new TypeField();
            Type type = typeof(TypeField);
            foreach (System.Xml.XmlNode node in xmlNode.ChildNodes)
            {
                PropertyInfo pi = type.GetProperty(node.Name);
                pi.SetValue(field, node.InnerText, null);
            }
            return field;
        }
    }
}
